/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package org.ant.gui.panes.ACtextpanel;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Cursor;
import java.awt.Desktop;
import java.awt.Dimension;
import java.awt.FileDialog;
import java.awt.Frame;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.Reader;
import java.net.URL;
import java.util.ArrayList;
import java.util.Hashtable;

import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JEditorPane;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JMenuBar;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JProgressBar;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.JTextPane;
import javax.swing.JToolBar;
import javax.swing.JViewport;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.event.HyperlinkEvent;
import javax.swing.event.HyperlinkListener;
import javax.swing.event.UndoableEditEvent;
import javax.swing.event.UndoableEditListener;
import javax.swing.text.BadLocationException;
import javax.swing.text.DefaultHighlighter;
import javax.swing.text.DefaultStyledDocument;
import javax.swing.text.Document;
import javax.swing.text.Highlighter;
import javax.swing.text.JTextComponent;
import javax.swing.text.PlainDocument;
import javax.swing.text.SimpleAttributeSet;
import javax.swing.text.StyleConstants;
import javax.swing.text.StyleContext;
import javax.swing.text.TextAction;
import javax.swing.text.DefaultHighlighter.DefaultHighlightPainter;
import javax.swing.undo.CannotRedoException;
import javax.swing.undo.CannotUndoException;
import javax.swing.undo.UndoManager;

import org.ant.gui.panes.ACtextpanel.Actions.AJCtextPanelAction;
import org.ant.gui.panes.ACtextpanel.listener.CodeListener;
import org.ant.gui.panes.ACtextpanel.listener.DocumentChangeListener;
import org.ant.gui.panes.ACtextpanel.types.TokenStyles;
import org.ant.gui.panes.ACtextpanel.types.TokenType;


import org.ant.util.Code;
import org.ant.util.ResourceHandler;

/**
 *
 * @author hichens
 */
public class ACTextPanel extends JPanel implements CodeEditPane,ActionListener{

	private StyleContext styleContext;
	private DefaultStyledDocument document;
	private JTextPane editor;

	private JScrollPane scroller;
	private LineNumberPanel linePanel;

	private SimpleAttributeSet defaultAttributes ;

	private CodeSearchHandler searchHandler;
	private TokenStyles styles;

	private Hashtable<Object,Action> commands;

	protected UndoableEditListener undoHandler = new UndoHandler();
	protected UndoManager undoManager = new UndoManager();

	private UndoAction undoAction = new UndoAction();
	private RedoAction redoAction = new RedoAction();
	private NewAction  newAction = new NewAction();
	private OpenAction openAction = new OpenAction();
	private SaveAction saveAction = new SaveAction();
	//private ExitAction exitAction = new ExitAction();
	private Action[] defaultActions = {undoAction,redoAction};



	public static final String showElementTreeAction = "showElementTree";

	boolean documentChanged;

	public ACTextPanel(){
		super();

		initEditor();
		initLayout();

		commands = new Hashtable<Object,Action>();
		Action[] actions = getActions();
		for (int i = 0; i < actions.length; i++) {
			Action a = actions[i];
			commands.put(a.getValue(Action.NAME), a);
		}

		styles = new TokenStyles();
		searchHandler = new CodeSearchHandler();

		defaultAttributes = new SimpleAttributeSet();                                        
		StyleConstants.setForeground(defaultAttributes, Color.black);        
		StyleConstants.setUnderline(defaultAttributes, false);
		StyleConstants.setBold(defaultAttributes, false);

		addListeners();
		this.setName("EGG code");

	}

	private void initEditor(){
		styleContext = new StyleContext();
		document = new DefaultStyledDocument(styleContext);
		DocumentChangeListener documentListener = new DocumentChangeListener(this);
		document.addDocumentListener(documentListener);
		editor = new JTextPane(document);
		editor.addCaretListener(documentListener);
		editor.setDragEnabled(true);
		editor.getDocument().addUndoableEditListener(undoHandler);
	}

	private void initLayout(){
		setBorder(BorderFactory.createEtchedBorder());
		setLayout(new BorderLayout());

		scroller = new JScrollPane();
		JViewport port = scroller.getViewport();

		JPanel panel = new JPanel();
		linePanel = new LineNumberPanel();
		panel.setLayout(new BorderLayout());
		panel.add(BorderLayout.CENTER, editor);
		panel.add(BorderLayout.WEST, linePanel);
		port.add(panel);
		add(BorderLayout.CENTER, scroller);
	}

	private void addListeners(){
		this.addMouseListener(new MouseAdapter() {
			public void mouseEntered(MouseEvent e) {

			}
			public void mouseExited(MouseEvent e) {

			}
			public void mouseClicked(MouseEvent e) {
				searchHandler.removeHighlights(editor);
			}
		});

		this.editor.addKeyListener(new KeyAdapter(){
			public   void   keyPressed(KeyEvent evt)   {  
				if (evt.getKeyCode()== KeyEvent.VK_CONTROL){
					//toLine(10);
				}else if (evt.getKeyCode()== KeyEvent.VK_SHIFT){
					//redo();
				}
			} 
		});
	}

	/**
	 * Fetch the list of actions supported by this
	 * editor.  It is implemented to return the list
	 * of actions supported by the embedded JTextComponent
	 * augmented with the actions defined locally.
	 */
	public Action[] getActions() {
		return TextAction.augmentList(editor.getActions(), defaultActions);
	}

	public Action getAction(String name){
		return commands.get(name);
	}


	public void updateHighlights(){
		Highlighter highlighter = editor.getHighlighter();
		highlighter.removeAllHighlights();
		String text = editor.getText().replaceAll("\r\n","\n");
		document.setCharacterAttributes(0, text.length(),defaultAttributes,true);//设置文本中的要使用该约束的字符串

		//语法高亮显示
		for (TokenType type:styles.getTypes()) {
			for (int j = 0; j < text.length() - (type.getName().length() - 1); j++) {
				String ch = text.substring(j, j+ type.getName().toString().length());
				if ((type.getName()).compareTo(ch) == 0) {
					int begin = j;
					int end = j+ type.getName().toString().length();
					boolean isBegin = (j==0)||(text.charAt(begin-1)==' '||text.charAt(begin-1)=='\n'||text.charAt(begin-1)=='\t');
					boolean isEnd = (end==text.length())||(text.charAt(end)==' '||text.charAt(end)=='\n'||text.charAt(end)=='\r'||text.charAt(end)=='\t');
					if (isBegin&&isEnd){
						try {
							document.setCharacterAttributes(j, type.getName().toString().length(),type.getAttributes(),true);
						} catch (Exception ble) {        //BadLocationException
							System.out.println("Exception happens while highlighting!");
							ble.printStackTrace();
						}
					}
				}
			}
		}
	}


	public void select(int selectionStart,int selectionEnd,Color c){
		editor.select(selectionStart, selectionEnd);
		DefaultHighlightPainter painter = new DefaultHighlighter.DefaultHighlightPainter(c);
		try {
			editor.getHighlighter().addHighlight(selectionStart, selectionEnd, painter);
		} catch (BadLocationException e) {
			e.printStackTrace();
		}
	}

	@Override
	public void actionPerformed(ActionEvent arg0) {
		//editor.setCaretPosition(0);  
		//LinkLabel link = new LinkLabel("abc","http://www.google.com");
		//editor.insertComponent(link);
		//link.setAlignmentY(0.75f);
	}


	protected void resetUndoManager() {
		undoManager.discardAllEdits();
		undoAction.update();
		redoAction.update();
	}



	public void documentChange() {
		documentChanged = true;
	}



	public void updateLines(){
		this.linePanel.setTotalNum(countLines()+1);
	}

	class UndoAction extends AbstractAction {

		public UndoAction() {
			super("Undo");
			setEnabled(false);
		}

		public void actionPerformed(ActionEvent e) {
			try {
				undoManager.undo();
			} catch (CannotUndoException ex) {
				System.out.println("Unable to undo: " + ex);
				ex.printStackTrace();
			}
			update();
			redoAction.update();
		}

		protected void update() {
			if (undoManager.canUndo()) {
				setEnabled(true);
				putValue(Action.NAME, undoManager.getUndoPresentationName());
			} else {
				setEnabled(false);
				putValue(Action.NAME, "Undo");
			}
		}
	}

	class RedoAction extends AbstractAction {

		public RedoAction() {
			super("Redo");
			setEnabled(false);
		}

		public void actionPerformed(ActionEvent e) {
			try {
				undoManager.redo();
			} catch (CannotRedoException ex) {
				System.out.println("Unable to redo: " + ex);
				ex.printStackTrace();
			}
			update();
			undoAction.update();
		}

		protected void update() {
			if (undoManager.canRedo()) {
				setEnabled(true);
				putValue(Action.NAME, undoManager.getRedoPresentationName());
			} else {
				setEnabled(false);
				putValue(Action.NAME, "Redo");
			}
		}
	}

	class OpenAction extends NewAction {

		OpenAction() {
			super("open");
		}

		public void actionPerformed(ActionEvent e) {
			/*
	        Frame frame = getFrame();
	        JFileChooser chooser = new JFileChooser();
	        int ret = chooser.showOpenDialog(frame);

	        if (ret != JFileChooser.APPROVE_OPTION) {
	            return;
	        }

	        File f = chooser.getSelectedFile();
	        if (f.isFile() && f.canRead()) {
	            Document oldDoc = getEditor().getDocument();
	            if (oldDoc != null) {
	                oldDoc.removeUndoableEditListener(undoHandler);
	            }
	            if (elementTreePanel != null) {
	                elementTreePanel.setEditor(null);
	            }
	            getEditor().setDocument(new PlainDocument());
	            frame.setTitle(f.getName());
	            Thread loader = new FileLoader(f, editor.getDocument());
	            loader.start();
	        } else {
	            JOptionPane.showMessageDialog(getFrame(),
	                    "Could not open file: " + f,
	                    "Error opening file",
	                    JOptionPane.ERROR_MESSAGE);
	        }
			 */
		}
	}

	class SaveAction extends AbstractAction {

		SaveAction() {
			super("save");
		}

		public void actionPerformed(ActionEvent e) {
			/*
	        Frame frame = getFrame();
	        JFileChooser chooser = new JFileChooser();
	        int ret = chooser.showSaveDialog(frame);

	        if (ret != JFileChooser.APPROVE_OPTION) {
	            return;
	        }

	        File f = chooser.getSelectedFile();
	        frame.setTitle(f.getName());
	        Thread saver = new FileSaver(f, editor.getDocument());
	        saver.start();
			 */
		}
	}

	class NewAction extends AbstractAction {
		NewAction() {
			super("new");
		}

		NewAction(String nm) {
			super(nm);
		}

		public void actionPerformed(ActionEvent e) {
			Document oldDoc = editor.getDocument();
			if (oldDoc != null) {
				oldDoc.removeUndoableEditListener(undoHandler);
			}
			editor.setDocument(new PlainDocument());
			editor.getDocument().addUndoableEditListener(undoHandler);
			resetUndoManager();
			revalidate();
		}
	}

	/**
	 * Really lame implementation of an exit command
	 */
	class ExitAction extends AbstractAction {

		ExitAction() {
			super("exit");
		}

		public void actionPerformed(ActionEvent e) {
			System.exit(0);
		}
	}

	class UndoHandler implements UndoableEditListener {

		/**
		 * Messaged when the Document has created an edit, the edit is
		 * added to <code>undo</code>, an instance of UndoManager.
		 */
		public void undoableEditHappened(UndoableEditEvent e) {
			undoManager.addEdit(e.getEdit());
			undoAction.update();
			redoAction.update();
		}
	}

	@Override
	public void addCodeListener(CodeListener listener) {
		// TODO Auto-generated method stub

	}

	@Override
	public void clear() {
		this.editor.setText("");
	}

	@Override
	public void copy() {
		commands.get(AJCtextPanelAction.copy_to_clipboard).actionPerformed(new ActionEvent(this, 0, "copy"));

	}

	@Override
	public void cut() {
		commands.get(AJCtextPanelAction.cut_to_clipboard).actionPerformed(new ActionEvent(this, 0, "cut"));
	}

	@Override
	public void paste() {
		commands.get(AJCtextPanelAction.paste_from_clipboard).actionPerformed(new ActionEvent(this, 0, "paste"));
	}

	@Override
	public String getText() {
		Document doc = editor.getDocument();
		try {
			return doc.getText(0, doc.getLength());
		} catch (BadLocationException e) {
			e.printStackTrace();
			return null;
		}
	}

	@Override
	public void load(String path){
		try {
			FileReader fr = new FileReader(path);
			StringBuffer temp = new StringBuffer();
			while (fr.ready()) {
				temp.append((char)fr.read());
			}
			editor.setText(temp.toString());
			fr.close();
			this.setName(path);
			this.updateLines();
		}catch (IOException e) {
			e.printStackTrace();
		} 
	}

	@Override
	public void save(String path) {
		// TODO Auto-generated method stub

	}

	@Override
	public void toLine(int n) {
		if (n>this.countLines()){
			return;
		}else{
			commands.get(AJCtextPanelAction.caret_begin).actionPerformed(new ActionEvent(this, 0, null));
			for (int i=0;i<n-1;i++){
				commands.get(AJCtextPanelAction.caret_end_line).actionPerformed(new ActionEvent(this, 0, null));
				commands.get(AJCtextPanelAction.caret_forward).actionPerformed(new ActionEvent(this, 0, null));
			}
		}
	}

	@Override
	public void redo() {
		commands.get(AJCtextPanelAction.Redo).actionPerformed(new ActionEvent(this, 0, "redo"));
	}

	@Override
	public void undo() {
		commands.get(AJCtextPanelAction.Undo).actionPerformed(new ActionEvent(this, 0, "undo"));
	}

	@Override
	public void hightLight(String s) {
		searchHandler.highlight(this.editor,s);
	}

	@Override
	public void clearError(int n) {
		this.linePanel.clearError(n);

	}

	@Override
	public void setError(int n, String error) {
		this.linePanel.setError(n, error);

	}

	@Override
	public int countLines(){
		int currentPosition = editor.getCaretPosition();
		int num = 0;
		commands.get(AJCtextPanelAction.caret_begin).actionPerformed(new ActionEvent(this, 0, null));
		while (editor.getCaretPosition()<editor.getDocument().getLength()){
			num++;
			commands.get(AJCtextPanelAction.caret_end_line).actionPerformed(new ActionEvent(this, 0, null));
			commands.get(AJCtextPanelAction.caret_forward).actionPerformed(new ActionEvent(this, 0, null));
		}
		editor.setCaretPosition(currentPosition);
		//System.out.println(num+" lines.");
		return num;
	}


	@Override
	public void setCodeModel(Code code){
		//TODO
	}
} 





